import time
import datetime
import torch
import os
import ntpath
from options.train_options import TrainOptions
from data import create_dataset
from models import create_model
from util.visualizer import Visualizer
import util.util as util
from tqdm import tqdm
from pytorch_fid import fid_score
from collections import OrderedDict


if __name__ == '__main__':
    opt = TrainOptions().parse()   # get training options
    dataset = create_dataset(opt)  # create a dataset given opt.dataset_mode and other options
    dataset_size = len(dataset)    # get the number of images in the dataset.

    model = create_model(opt)      # create a model given opt.model and other options
    print('The length of training datasets = %d' % dataset_size)
    time_str = str(datetime.datetime.now().strftime('_%m%d%H%M'))
    opt.display_env = opt.name + time_str
    visualizer = Visualizer(opt)   # create a visualizer that display/save images and plots
    opt.visualizer = visualizer
    total_iters = 0                # the total number of training iterations

    optimize_time = 0.1
    times = []

    # cal fid related
    test_dataset = create_dataset(util.copyconf(opt, phase = "test", batch_size = 1))
    test_dataset_size = len(test_dataset)
    print('The length of testing datasets = %d' % test_dataset_size)
    data_B_dir = os.path.join(opt.dataroot, 'test/B')    
    batch_size = test_dataset_size 
    device = 'cuda' if torch.cuda.is_available() else 'cpu'  # 使用 GPU 如果可用
    dims = 2048  

    for epoch in range(opt.epoch_count, opt.n_epochs + opt.n_epochs_decay + 1):    # outer loop for different epochs; we save the model by <epoch_count>, <epoch_count>+<save_latest_freq>
        epoch_start_time = time.time()  # timer for entire epoch
        iter_data_time = time.time()    # timer for data loading per iteration
        epoch_iter = 0                  # the number of training iterations in current epoch, reset to 0 every epoch
        # visualizer.reset()              # reset the visualizer: make sure it saves the results to HTML at least once every epoch

        dataset.set_epoch(epoch)
        model.train()
        for i, data in enumerate(dataset):  # inner loop within one epoch
            iter_start_time = time.time()  # timer for computation per iteration
            if total_iters % opt.print_freq == 0:
                t_data = iter_start_time - iter_data_time

            batch_size = data["A"].size(0)
            total_iters += batch_size
            epoch_iter += batch_size
            if len(opt.gpu_ids) > 0:
                torch.cuda.synchronize()
            optimize_start_time = time.time()
            if epoch == opt.epoch_count and i == 0:
                model.data_dependent_initialize(data)
                model.setup(opt)               # regular setup: load and print networks; create schedulers
                model.parallelize()
            model.set_input(data)  # unpack data from dataset and apply preprocessing
            model.optimize_parameters(total_iters)   # calculate loss functions, get gradients, update network weights
            if len(opt.gpu_ids) > 0:
                torch.cuda.synchronize()
            optimize_time = (time.time() - optimize_start_time) / batch_size * 0.005 + 0.995 * optimize_time

            if model.best_loss is not None and total_iters % opt.display_freq == 0:
                if model.best_loss > model.loss_D_B:
                    model.best_loss = model.loss_D_B
                    print('saving the best model (epoch %d, total_iters %d)' % (epoch, total_iters))
                    save_suffix = 'best'
                    model.save_networks(save_suffix)

            if total_iters % opt.display_freq == 0:   # display images on visdom and save images to a HTML file
                save_result = total_iters % opt.update_html_freq == 0
                model.compute_visuals()
                visualizer.display_current_results(model.get_current_visuals(), epoch, save_result)

            if total_iters % opt.print_freq == 0:    # print training losses and save logging information to the disk
                losses = model.get_current_losses()
                visualizer.print_current_losses(epoch, epoch_iter, losses, optimize_time, t_data)
                if opt.display_id is None or opt.display_id > 0:
                    visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, losses)

            iter_data_time = time.time()
        
        if epoch % opt.save_epoch_freq == 0:              # cache our model every <save_epoch_freq> epochs
            print('saving the model at the end of epoch %d, iters %d' % (epoch, total_iters))
            model.save_networks('latest')
            model.save_networks(epoch)

        print('End of epoch %d / %d \t Time Taken: %d sec' % (epoch, opt.n_epochs + opt.n_epochs_decay, time.time() - epoch_start_time))
        model.update_learning_rate()                     # update learning rates at the end of every epoch.

        #####################################test########################################
        ssim_epoch = 0
        fid_epoch = 0
        test_save_dir = os.path.join(opt.checkpoints_dir, opt.name, 'test_images', 'test_{}'.format(epoch))
        print('creating web directory = %s, Epoch = %s'%( test_save_dir, epoch))
        os.makedirs(test_save_dir, exist_ok=True)
        # 在test集上跑推理，并save to disk, 在visdom展示ssim和fid
        #  ssim fid write log
        for i, data in tqdm(enumerate(test_dataset)):
            model.eval()
            model.set_input(data)  # unpack data from data loader
            model.test()
            # calc ssim value
            ssim_epoch += model.compute_ssim()
            # save test results on disk
            visuals = model.get_test_visuals()  # get image results
            img_path = model.get_image_paths()     # get image paths
            short_path = ntpath.basename(img_path[0])
            name = os.path.splitext(short_path)[0]
            for label, im_data in visuals.items():
                if im_data.ndim ==3:
                    im_data = im_data.unsqueeze(0)
                im = util.tensor2im(im_data)
                save_path = os.path.join(test_save_dir, name+".png")
                util.save_image(im, save_path)  
        
        ssim_epoch /= test_dataset_size
        # cal fid
        fid_epoch = fid_score.calculate_fid_given_paths([data_B_dir, test_save_dir],
                                                    batch_size=batch_size,
                                                    device=device,
                                                    dims=dims)
        # plot ssim
        # evals = model.get_current_evals()
        ssim_dict = OrderedDict()
        ssim_dict['ssim'] = float(ssim_epoch) 
        visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, ssim_dict)
        visualizer.print_current_losses(epoch, epoch_iter, ssim_dict, 0, 0)

        fid_dict = OrderedDict()
        fid_dict['fid'] = float(fid_epoch) 
        # metrics = model.get_current_metrics()
        visualizer.plot_current_losses(epoch, float(epoch_iter) / dataset_size, fid_dict)
        visualizer.print_current_losses(epoch, epoch_iter, fid_dict, 0, 0)
